﻿using System.Collections.Generic;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

namespace DBUtil;

public interface IIdSNOGenerator
{
    Task<long> NewIdAsync(DBAccess db, string tableName, string colName, CancellationToken cancellationToken = default);
    Task<long[]> NewIdsAsync(DBAccess db, string tableName, string colName, int count, CancellationToken cancellationToken = default);

    Task<string> NewSNOAsync(DBAccess db, string tableName, string colName, SerialFormat serialFormat, CancellationToken cancellationToken = default);
    Task<string[]> NewSNOsAsync(DBAccess db, string tableName, string colName, SerialFormat serialFormat, int count, CancellationToken cancellationToken = default);
}

/// <summary>
/// 流水块定义格式
/// </summary>
public class SerialFormatChunk
{
    /// <summary>
    /// 流水块类型
    /// </summary>
    public SerialFormatChunkType Type { set; get; }
    /// <summary>
    /// 流水块格式
    /// </summary>
    public string FormatString { set; get; }
    /// <summary>
    /// 流水块长度
    /// </summary>
    public int Length { set; get; }
}

/// <summary>
/// 流水块类型
/// </summary>
public enum SerialFormatChunkType
{
    /// <summary>
    /// 静态文本
    /// </summary>
    StaticText,
    /// <summary>
    /// 日期
    /// </summary>
    DateText,
    /// <summary>
    /// 机器码
    /// </summary>
    MachineText,
    /// <summary>
    /// 序列号
    /// </summary>
    SerialNo
}

/// <summary>
///流水号格式<br />
///<list type="bullet">
///<item>有且只有一个序列号块(SerialNo),并且在末尾；</item>
///<item>至少有一个时间格式块(DateText)；</item>
/// </list>
/// </summary>
public class SerialFormat
{
    private SerialFormat() { }
    /// <summary>
    /// 创建流水号格式
    /// </summary>
    /// <param name="prefix">流水号前缀</param>
    /// <param name="dateFormat">日期格式串</param>
    /// <param name="minSerialLen">流水号末尾序列块的最小长度；参考：<seealso cref="SerialFormat"/></param>
    /// <param name="hasMachine">是否启用机器码(默认不启用,分布式环境下务必启用!)</param>
    /// <returns></returns>
    public static SerialFormat CreateFast(string prefix, string dateFormat = "yyyyMMdd", int minSerialLen = 6, bool hasMachine = false)
    {
        var format = new SerialFormat()
        {
            Chunks = new List<SerialFormatChunk>()
            {
                new SerialFormatChunk()
                {
                    Type=SerialFormatChunkType.StaticText,
                    FormatString=prefix
                },
                new SerialFormatChunk()
                {
                    Type=SerialFormatChunkType.DateText,
                    FormatString=dateFormat
                }
            }
        };
        if (hasMachine)
        {
            format.Chunks.Add(new SerialFormatChunk()
            {
                Type = SerialFormatChunkType.MachineText
            });
        }
        format.Chunks.Add(new SerialFormatChunk()
        {
            Type = SerialFormatChunkType.SerialNo,
            Length = minSerialLen
        });
        return format;
    }

    /// <summary>
    /// 创建流水号格式,启用分布式生成
    /// </summary>
    /// <param name="prefix">流水号前缀</param>
    /// <param name="dateFormat">日期格式串</param>
    /// <param name="minSerialLen">流水号末尾序列块的最小长度；参考：<seealso cref="SerialFormat"/></param>
    /// <returns></returns>
    public static SerialFormat CreateDistributeFast(string prefix, string dateFormat = "yyyyMMdd", int minSerialLen = 6) => CreateFast(prefix, dateFormat, minSerialLen, true);

    /// <summary>
    /// 创建流水号格式
    /// </summary>
    /// <param name="chunks"></param>
    /// <returns></returns>
    public static SerialFormat CreateByChunks(List<SerialFormatChunk> chunks)
    {
        var format = new SerialFormat() { Chunks = chunks };
        ValidFormat(format);
        return format;
    }

    /// <summary>
    /// 解析指定的流水号规则
    /// </summary>
    /// <param name="format">流水号格式</param>
    /// <param name="now">当前日期</param>
    /// <param name="machineIdString">机器Id</param>
    /// <returns>(string likeStr, DateTime snoNow, int startIndex)</returns>
    public static (string likeStr, DateTime snoNow, int startIndex) Parse(SerialFormat format, DateTime? now, string machineIdString)
    {
        if (now == null) now = DateTime.Now;
        ValidFormat(format);
        int startindex = 0;
        DateTime snoNow = DateTime.MinValue;
        var likestr = "";
        var chunks = format.Chunks;
        for (int i = 0, len = chunks.Count; i < len; i++)
        {
            var chunk = chunks[i];
            if (chunk.Type == SerialFormatChunkType.StaticText)
            {
                likestr += chunk.FormatString;
            }
            else if (chunk.Type == SerialFormatChunkType.DateText)
            {
                var nowstr = now.Value.ToString(chunk.FormatString);
                likestr += nowstr;
                snoNow = DateTime.ParseExact(nowstr, format.Chunks.FirstOrDefault(k => k.Type == SerialFormatChunkType.DateText).FormatString, System.Globalization.CultureInfo.CurrentCulture);
            }
            else if (chunk.Type == SerialFormatChunkType.MachineText)
            {
                likestr += machineIdString.PadLeft(4, '0');
            }
            else if (chunk.Type == SerialFormatChunkType.SerialNo)
            {
                startindex = likestr.Length;
                likestr += "%";
            }
        }
        return (likestr, snoNow, startindex);
    }

    /// <summary>
    /// 流水号定义格式块集合
    /// </summary>
    public List<SerialFormatChunk> Chunks { private set; get; }

    /// <summary>
    /// 检测流水号是否符合格式规则
    /// <list type="bullet">
    /// <item>有且只有一个序列号块(SerialNo),并且在末尾；</item>
    /// <item>至少有一个时间格式块(DateText)；</item>
    /// </list>
    /// </summary>
    /// <param name="format">流水号格式</param>
    /// <returns></returns>
    public static void ValidFormat(SerialFormat format)
    {
        if (format == null) throw new Exception("流水号格式为空!");
        if (format.Chunks == null && format.Chunks.Count == 0) throw new Exception("流水号格式块中至少存在一个日期格式块和序列号块!");
        var chunks = format.Chunks;
        if (chunks.FirstOrDefault(i => i.Type == SerialFormatChunkType.DateText) == null) throw new Exception("流水号格式中至少有一个日期格式块!");
        if (chunks[chunks.Count - 1].Type != SerialFormatChunkType.SerialNo) throw new Exception("流水号格式块的最后一个必须是序列号快!");
    }
}